Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stabilize naked_functions #134213

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

folkertdev
Copy link
Contributor

@folkertdev folkertdev commented Dec 12, 2024

tracking issue: #90957
request for stabilization on tracking issue: #90957 (comment)
reference PR: rust-lang/reference#1689

Request for Stabilization

Two years later, we're ready to try this again. Even though this issue is already marked as having passed FCP, given the amount of time that has passed and the changes in implementation strategy, we should follow the process again.

Summary

The naked_functions feature has two main parts: the #[naked] function attribute, and the naked_asm! macro.

An example of a naked function:

const THREE: usize = 3;

#[naked]
pub extern "sysv64" fn add_n(number: usize) -> usize {
    // SAFETY: the validity of the used registers 
    // is guaranteed according to the "sysv64" ABI
    unsafe {
        core::arch::naked_asm!(
            "add rdi, {}",
            "mov rax, rdi",
            "ret",
            const THREE,
        )
    }
}

When the #[naked] attribute is applied to a function, the compiler won't emit a function prologue or epilogue when generating code for this function. This attribute is analogous to __attribute__((naked)) in C. The use of this feature allows the programmer to have precise control over the assembly that is generated for a given function.

The body of a naked function must consist of a single naked_asm! invocation, a heavily restricted variant of the asm! macro: the only legal operands are const and sym, and the only legal options are raw and att_syntax. In lieu of specifying operands, the naked_asm! within a naked function relies on the function's calling convention to determine the validity of registers.

Documentation

The Rust Reference: rust-lang/reference#1689
(Previous PR: rust-lang/reference#1153)

Tests

Interaction with other (unstable) features

fn_align

Combining #[naked] with #[repr(align(N))] works well, and is tested e.g. here

It's tested extensively because we do need to explicitly support the repr(align) attribute (and make sure we e.g. don't mistake powers of two for number of bytes).

History

This feature was originally proposed in RFC 1201, filed on 2015-07-10 and accepted on 2016-03-21. Support for this feature was added in #32410, landing on 2016-03-23. Development languished for several years as it was realized that the semantics given in RFC 1201 were insufficiently specific. To address this, a minimal subset of naked functions was specified by RFC 2972, filed on 2020-08-07 and accepted on 2021-11-16. Prior to the acceptance of RFC 2972, all of the stricter behavior specified by RFC 2972 was implemented as a series of warn-by-default lints that would trigger on existing uses of the naked attribute; these lints became hard errors in #93153 on 2022-01-22. As a result, today RFC 2972 has completely superseded RFC 1201 in describing the semantics of the naked attribute.

More recently, the naked_asm! macro was added to replace the earlier use of a heavily restricted asm! invocation. The naked_asm! name is clearer in error messages, and provides a place for documenting the specific requirements of inline assembly in naked functions.

The implementation strategy was changed to emitting a global assembly block. In effect, an extern function

extern "C" fn foo() {
    core::arch::naked_asm!("ret")
}

is emitted as something similar to

core::arch::global_asm!( 
    "foo:",
    "ret"
);

extern "C" {
    fn foo();
}

The codegen approach was chosen over the llvm naked function attribute because:

  • the rust compiler can guarantee the behavior (no sneaky additional instructions, no inlining, etc.)
  • behavior is the same on all backends (llvm, cranelift, gcc, etc)

Finally, there is now an allow list of compatible attributes on naked functions, so that e.g. #[inline] is rejected with an error. The #[target_feature] attribute on naked functions was later made separately unstable, because implementing it is complex and we did not want to block naked functions themselves on how target features work on them. See also #138568.

relevant PRs for these recent changes

Various historical notes

noreturn

RFC 2972 mentions that naked functions

must have a body which contains only a single asm!() statement which:
iii. must contain the noreturn option.

Instead of asm!, the current implementation mandates that the body contain a single naked_asm! statement. The naked_asm! macro is a heavily restricted version of the asm! macro, making it easier to talk about and document the rules of assembly in naked functions and give dedicated error messages.

For naked_asm!, the behavior of the asm!'s noreturn option is implicit. The noreturn option means that it is UB for control flow to fall through the end of the assembly block. With asm!, this option is usually used for blocks that diverge (and thus have no return and can be typed as !). With naked_asm!, the intent is different: usually naked funtions do return, but they must do so from within the assembly block. The noreturn option was used so that the compiler would not itself also insert a ret instruction at the very end.

padding / ud2

A naked_asm! block that violates the safety assumption that control flow must not fall through the end of the assembly block is UB. Because no return instruction is emitted, whatever bytes follow the naked function will be executed, resulting in truly undefined behavior. There has been discussion whether rustc should emit an invalid instruction (e.g. ud2 on x86) after the naked_asm! block to at least fail early in the case of an invalid naked_asm!. It was however decided that it is more useful to guarantee that #[naked] functions NEVER contain any instructions besides those in the naked_asm! block.

unresolved questions

None

r? @Amanieu

I've validated the tests on x86_64 and aarch64

@rustbot rustbot added A-run-make Area: port run-make Makefiles to rmake.rs PG-exploit-mitigations Project group: Exploit mitigations S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Dec 12, 2024
@rustbot
Copy link
Collaborator

rustbot commented Dec 12, 2024

Some changes occurred in src/doc/unstable-book/src/compiler-flags/sanitizer.md

cc @rust-lang/project-exploit-mitigations, @rcvalle

This PR modifies tests/run-make/. If this PR is trying to port a Makefile
run-make test to use rmake.rs, please update the
run-make port tracking issue
so we can track our progress. You can either modify the tracking issue
directly, or you can comment on the tracking issue and link this PR.

cc @jieyouxu

rust-analyzer is developed in its own repository. If possible, consider making this change to rust-lang/rust-analyzer instead.

cc @rust-lang/rust-analyzer

Some changes occurred in compiler/rustc_codegen_cranelift

cc @bjorn3

@traviscross traviscross added T-lang Relevant to the language team, which will review and decide on the PR/issue. S-waiting-on-documentation Status: Waiting on approved PRs to documentation before merging needs-fcp This change is insta-stable, so needs a completed FCP to proceed. I-lang-nominated Nominated for discussion during a lang team meeting. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Dec 13, 2024
@bors
Copy link
Collaborator

bors commented Dec 16, 2024

☔ The latest upstream changes (presumably #134395) made this pull request unmergeable. Please resolve the merge conflicts.

@folkertdev folkertdev force-pushed the stabilize-naked-functions branch from 6183807 to ed0d0b9 Compare December 18, 2024 21:05
@folkertdev folkertdev force-pushed the stabilize-naked-functions branch from ed0d0b9 to ae2fa18 Compare January 8, 2025 18:39
@tgross35 tgross35 added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Jan 9, 2025
@Amanieu
Copy link
Member

Amanieu commented Jan 13, 2025

r? lang

@rustbot rustbot assigned tmandry and unassigned Amanieu Jan 13, 2025
@Amanieu
Copy link
Member

Amanieu commented Jan 13, 2025

This probably needs a new lang FCP since the old one is probably outdated (the implementation of naked function has changed signficantly).

@tmandry
Copy link
Member

tmandry commented Jan 18, 2025

Thanks for the thorough report @folkertdev!

@rfcbot fcp merge

@rfcbot
Copy link
Collaborator

rfcbot commented Jan 18, 2025

Team member @tmandry has proposed to merge this. The next step is review by the rest of the tagged team members:

Concerns:

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Jan 18, 2025
@tmandry
Copy link
Member

tmandry commented Jan 18, 2025

Actually, @rust-lang/libs-api does this need your FCP? I think the path of core::arch::naked_asm! is the only part that might.

@traviscross traviscross removed the PG-exploit-mitigations Project group: Exploit mitigations label Jan 27, 2025
@folkertdev
Copy link
Contributor Author

catching up to some comments


Of course, we could argue that, "well, you can't actually create unsafety without the inner unsafe { .. }", but still.

Yes that is what I would argue :)

from the edition guide

Rust 1.82 added the ability in all editions to mark certain attributes as unsafe to indicate that they have soundness requirements that must be upheld

There are no soundness requirements to a naked function itself. The requirements are on the naked_asm!, which is already covered by an unsafe block. What additional value does making the attribute itself unsafe have?


Also, just to make sure I understand correctly, the following would not compile, right?

#[naked]
pub extern "sysv64" fn f() {}

It is obviously unsound, without actually containing an unsafe block, as calling this function would start executing padding bytes, so it should be disallowed.

Correct, that is tested here:

#[naked]
pub extern "C" fn missing_assembly() {
//~^ ERROR naked functions must contain a single `naked_asm!` invocation


We can map our target feature names to the ones LLVM uses easily, we already do that. Is that not enough?

Sadly not. LLVM target features are also just made-up names that don't always map to the names that the assembler uses. E.g. the arm trustzone feature is known in the assembler as sec (for security, I guess). Most targets behave much more reasonably but some clearly don't.


Is there any reason for not just moving #[naked] with a #[target_feature] to a separate feature gate and stabilize naked_function without this for now?

That is my proposed course of action. So far nobody seems to really be against this approach, so if things stay that way I'll make a PR.

@RalfJung
Copy link
Member

RalfJung commented Mar 9, 2025

LLVM target features are also just made-up names that don't always map to the names that the assembler uses.

And I guess we can't send the naked function "through" LLVM to let it do the translation?

That might be a good feature request for them...

Is there any reason for not just moving #[naked] with a #[target_feature] to a separate feature gate and stabilize naked_function without this for now?

Sounds good to me.

@folkertdev
Copy link
Contributor Author

And I guess we can't send the naked function "through" LLVM to let it do the translation?

No that was the original design, but it was rejected for various reasons:

  • LLVM would insert additional instructions in some cases (or at least did not guarantee that it would not)
  • we have non-LLVM backends that would need to handle this logic too

Some further context from the tracking issue:

#90957 (comment)
#90957 (comment)

@RalfJung
Copy link
Member

RalfJung commented Mar 9, 2025

we have non-LLVM backends that would need to handle this logic too

As they should. They have to deal with target feature names anyway for regular code.

Anyway, I am mostly clueless here, just poking a bit in the dark to feel out the design space. @Amanieu knows a lot better than me how to make inline assembly work well. :)

@bors
Copy link
Collaborator

bors commented Mar 13, 2025

☔ The latest upstream changes (presumably #138450) made this pull request unmergeable. Please resolve the merge conflicts.

@folkertdev folkertdev force-pushed the stabilize-naked-functions branch from ae2fa18 to 1687743 Compare March 13, 2025 17:36
@rustbot rustbot added the PG-exploit-mitigations Project group: Exploit mitigations label Mar 13, 2025
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Mar 21, 2025
…eature-gate, r=Amanieu

add `naked_functions_target_feature` unstable feature

tracking issue: rust-lang#138568

tagging rust-lang#134213 rust-lang#90957

This PR puts `#[target_feature(/* ... */)]` on `#[naked]` functions behind its own feature gate, so that naked functions can be stabilized. It turns out that supporting `target_feature` on naked functions is tricky on some targets, so we're splitting it out to not block stabilization of naked functions themselves. See the tracking issue for more information and workarounds.

Note that at the time of writing, the `target_features` attribute is ignored when generating code for naked functions.

r? `@Amanieu`
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Mar 21, 2025
…eature-gate, r=Amanieu

add `naked_functions_target_feature` unstable feature

tracking issue: rust-lang#138568

tagging rust-lang#134213 rust-lang#90957

This PR puts `#[target_feature(/* ... */)]` on `#[naked]` functions behind its own feature gate, so that naked functions can be stabilized. It turns out that supporting `target_feature` on naked functions is tricky on some targets, so we're splitting it out to not block stabilization of naked functions themselves. See the tracking issue for more information and workarounds.

Note that at the time of writing, the `target_features` attribute is ignored when generating code for naked functions.

r? ``@Amanieu``
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Mar 21, 2025
Rollup merge of rust-lang#138570 - folkertdev:naked-function-target-feature-gate, r=Amanieu

add `naked_functions_target_feature` unstable feature

tracking issue: rust-lang#138568

tagging rust-lang#134213 rust-lang#90957

This PR puts `#[target_feature(/* ... */)]` on `#[naked]` functions behind its own feature gate, so that naked functions can be stabilized. It turns out that supporting `target_feature` on naked functions is tricky on some targets, so we're splitting it out to not block stabilization of naked functions themselves. See the tracking issue for more information and workarounds.

Note that at the time of writing, the `target_features` attribute is ignored when generating code for naked functions.

r? ``@Amanieu``
@bors
Copy link
Collaborator

bors commented Mar 21, 2025

☔ The latest upstream changes (presumably #138791) made this pull request unmergeable. Please resolve the merge conflicts.

@folkertdev folkertdev force-pushed the stabilize-naked-functions branch from 1687743 to 02cff1f Compare March 21, 2025 22:49
@folkertdev
Copy link
Contributor Author

The #[target_feature] attribute on naked functions is now separately guarded by the naked_functions_target_feature feature. I think that should resolve the concern here around target features.

tracking issue: #138568
PR: #138570


Looking at the other concerns, noreturn has been added to the historical notes in the stabilization report, and padding/ud2 are now mentioned there too. Unless there is something else, I think these are also resolved?

The current remaining concerns are then:

  • question-about-rust-abi: looks like the consensus is that using the rust abi should not be explicitly disallowed, even though it is extremely unsafe to use it? Note that to use it, you must use extern "Rust" fn, a standard fn triggers a lint.
  • maybe-unsafe-attribute: it's been discussed. I don't personally see much value in it, but if anyone wants to really argue in favor, please do?

As they should. They have to deal with target feature names anyway for regular code.

Ah, I misunderstood this earlier. What I meant is that we could have used llvm's native support for naked functions, which has the downsides that I and Amanieu mentioned.

I think you propose to still use global assembly, but somehow inform the backend of the target features. That would be neat, but can't currently be done even in LLVM.

@onestacked
Copy link
Contributor

Is there a practical reason to allow extern "Rust" fn?

I think unless there is a specific need for it it would be best to disallow it (From an mostly outsider perspective, I'm not really familiar with the state of Rust ABI and the current state of naked functions).
From what I understand Rust ABI is not stable, so I don't expect it to be very useful but I might be missing something?

@tgross35
Copy link
Contributor

Linting on extern "Rust" fn seems reasonable to me, but I don't think we should forbid it. It's useful for experimentation and possibly internal tests, if nothing else.

@traviscross
Copy link
Contributor

The #[target_feature] attribute on naked functions is now separately guarded by the naked_functions_target_feature feature. I think that should resolve the concern here around target features.

tracking issue: #138568
PR: #138570

Sounds right. Thanks for filing the new tracking issue.

@rfcbot resolve what-to-do-for-target-feature


Looking at the other concerns, noreturn has been added to the historical notes in the stabilization report, and padding/ud2 are now mentioned there too. Unless there is something else, I think these are also resolved?

They are. Thanks for adding these notes. The rationales and outcomes there seem right to me.

@rfcbot resolve question-about-noreturn
@rfcbot resolve mention-nop-padding-and-ud2


The current remaining concerns are then...

These probably need to be discussed with the team. We'll take them up in a lang call.

@scottmcm
Copy link
Member

On the ud2 point: If debug mode rustc always arranged that functions were laid out such that there just so happened to be a ud2 after every naked functions, that would still be fine, right?

Not requiring that certainly makes sense to me, though, especially for opt-level=z.

@traviscross
Copy link
Contributor

traviscross commented Mar 26, 2025

OK. On the lang call today, we reviewed all of the concerns resolved above, and we worked through the two remaining items. Here's what we want to do.

One, we don't want to stabilize naked functions with the "Rust" ABI as part of this stabilization. We'd like that (and all rustic ABIs) carved off into a separate feature flag and tracking issue. Then, if we find there's some use case for this that doesn't rely on details to which we're not willing to commit, we can later consider that on its own.

One reason for this is caution due to Hyrum's law:

With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.

Because of the nature of naked functions, it's difficult to use them without relying on details of the ABI, and we don't want to later have this conversation when making changes to the Rust ABI:

Bob: Wait, you can't change that! I've written a bunch of naked functions that are possibly broken now.

Alice: We didn't guarantee any details about the Rust ABI though.

Bob: Then why did you stabilize naked functions for the Rust ABI? There's no way to use them without relying on those details.

By contrast, someone having to reach for nightly to do this sends the right signal.


Two, we want to move the unsafe out of the body and into the attribute. That is, naked will be an unsafe attribute, and the naked_asm! macro -- which can only appear within a #[unsafe(naked)] function (and is required to do so and is the only thing that can appear within such a function) -- does not need to be wrapped in an unsafe { .. } block within the function.

We had a good long and hearty discussion about this one looking at it from every angle. The deciding factor here is in thinking about the proper scope of the unsafe obligation being discharged. What we're really trying to say is in fact:

unsafe {
    #[naked]
    extern "sysv64" fn f() {
        naked_asm! {
            ..
        }
    }
}

That is, the author of the function needs to prove certain properties about the function as a whole, inclusive of and affected by that naked attribute. In particular, one needs to prove it's upholding its signature. Naked functions are particularly special here, as the naked attribute is removing the function prelude, which is obviously unsafe, and the code in the function body then has to work, according to the stated ABI, to satisfy the signature all while not doing UB. In this way, the naked attribute and the naked_asm! block essentially form one atomic unit of unsafe obligations that must be discharged.

Therefore it makes sense to move the unsafe outward to the attribute and say that what we mean in doing that is to extend the scope of discharge to the entire function, exactly as if we had item-level unsafe, as above. So we'll write:

#[unsafe(naked)]
extern "sysv64" fn f() {
    naked_asm! {
        ..
    }
}

Pleasingly, this also avoids some rightward drift.


I'll go ahead and resolve the concerns as we've answered the questions here.

@rfcbot resolve maybe-unsafe-attribute
@rfcbot resolve question-about-rust-abi

Please update the PR (and the Reference PR) accordingly and hand it back to me.

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 26, 2025
@rfcbot rfcbot added the final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. label Mar 26, 2025
@rfcbot
Copy link
Collaborator

rfcbot commented Mar 26, 2025

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. label Mar 26, 2025
@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Apr 5, 2025
@rfcbot
Copy link
Collaborator

rfcbot commented Apr 5, 2025

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-run-make Area: port run-make Makefiles to rmake.rs disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. I-lang-nominated Nominated for discussion during a lang team meeting. needs-fcp This change is insta-stable, so needs a completed FCP to proceed. PG-exploit-mitigations Project group: Exploit mitigations S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. S-waiting-on-documentation Status: Waiting on approved PRs to documentation before merging T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. to-announce Announce this issue on triage meeting
Projects
None yet
Development

Successfully merging this pull request may close these issues.